/*
 * Copyright (c) 2007 Aleksey Nikiforov
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Aleksey Nikiforov' nor the names of other contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lex.input;

import java.util.HashMap;

import org.lex.input.effects.ArchInterpolationController;
import org.lex.input.effects.InterpolationController;
import org.lex.input.effects.interpolation.SpringQuaternionInterpolation;
import org.lex.input.effects.interpolation.SpringVector3fInterpolation;

import com.jme.input.InputHandler;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.CameraNode;

/**
 * This is an optional StrategicHandler addon that introduces some cool camera
 * effects beyond the basic functionality.
 * 
 * @author lex
 *
 */
public class StrategicCameraEffects implements CameraEffects {
	
	private static final float DEFAULT_MASS = 1;
	
	public static final float DEFAULT_TAKEOVER_LOC_SPRING = 40;
	public static final float DEFAULT_TAKEOVER_LOC_DAMPING = 10;
	public static final float DEFAULT_TAKEOVER_LOC_ACCELFACTOR = 1000;
	public static final float DEFAULT_TAKEOVER_LOC_MAXSPEED = 0;
	public static final float DEFAULT_TAKEOVER_ROT_SPRING = 80;
	public static final float DEFAULT_TAKEOVER_ROT_DAMPING = 10;
	public static final float DEFAULT_TAKEOVER_ROT_ACCELFACTOR = 2000;
	public static final float DEFAULT_TAKEOVER_ROT_MAXSPEED = 0;
	
	public static final float DEFAULT_FLYTO_LOC_SPRING = 20;
	public static final float DEFAULT_FLYTO_LOC_DAMPING = 10;
	public static final float DEFAULT_FLYTO_LOC_ACCELFACTOR = 500;
	public static final float DEFAULT_FLYTO_LOC_MAXSPEED = 1200;
	public static final float DEFAULT_FLYTO_ROT_SPRING = 80;
	public static final float DEFAULT_FLYTO_ROT_DAMPING = 10;
	public static final float DEFAULT_FLYTO_ROT_ACCELFACTOR = 2000;
	public static final float DEFAULT_FLYTO_ROT_MAXSPEED = 0;
	public static final float DEFAULT_FLYTO_ARCH_FACTOR = 0.7f;
	
	
	protected StrategicHandler strategicHandler;
	protected Vector3f worldUp;
	protected ScrollingPlane plane;
	protected RotationSphere sphere;
	
	private Vector3f camPosition;
	private Vector3f camLookAt;
	protected CameraNode cameraNode;
	
	// temp variables
	protected Vector2f location = new Vector2f();
	protected Vector3f temp = new Vector3f();
	
	private SpringVector3fInterpolation locInterpolation;
	private SpringQuaternionInterpolation rotInterpolation;
	private SpringVector3fInterpolation archLocInterpolation;
	private SpringQuaternionInterpolation archRotInterpolation;
	
	protected InterpolationController camController;
	protected ArchInterpolationController archCamController;
	
	public StrategicCameraEffects(StrategicHandler strategicHandler) {
		this.strategicHandler = strategicHandler;
		worldUp = strategicHandler.getRotationSphere().getWorldUp();
		plane = strategicHandler.getScrollingPlane();
		sphere = strategicHandler.getRotationSphere();
		
		cameraNode = new CameraNode(
				"StrategicCameraEffects.cameraNode", plane.getCamera());
		
		// cam controller
		locInterpolation = new SpringVector3fInterpolation(
					DEFAULT_MASS,
					DEFAULT_TAKEOVER_LOC_SPRING,
					DEFAULT_TAKEOVER_LOC_DAMPING);
		locInterpolation.setAccelerationFactor(
				DEFAULT_TAKEOVER_LOC_ACCELFACTOR);
		locInterpolation.setMaxSpeed(
				DEFAULT_TAKEOVER_LOC_MAXSPEED);
		
		rotInterpolation = new SpringQuaternionInterpolation(
					DEFAULT_MASS,
					DEFAULT_TAKEOVER_ROT_SPRING,
					DEFAULT_TAKEOVER_ROT_DAMPING);
		rotInterpolation.setAccelerationFactor(
				DEFAULT_TAKEOVER_ROT_ACCELFACTOR);
		rotInterpolation.setMaxSpeed(
				DEFAULT_TAKEOVER_ROT_MAXSPEED);
		
		camController = new InterpolationController(cameraNode,
				locInterpolation, rotInterpolation, worldUp);
		cameraNode.addController(camController);

		// arch controller
		archLocInterpolation = new SpringVector3fInterpolation(
					DEFAULT_MASS,
					DEFAULT_FLYTO_LOC_SPRING,
					DEFAULT_FLYTO_LOC_DAMPING);
		archLocInterpolation.setAccelerationFactor(
				DEFAULT_FLYTO_LOC_ACCELFACTOR);
		archLocInterpolation.setMaxSpeed(
				DEFAULT_FLYTO_LOC_MAXSPEED);
		
		archRotInterpolation = new SpringQuaternionInterpolation(
					DEFAULT_MASS,
					DEFAULT_FLYTO_ROT_SPRING,
					DEFAULT_FLYTO_ROT_DAMPING);
		archRotInterpolation.setAccelerationFactor(
				DEFAULT_FLYTO_ROT_ACCELFACTOR);
		archRotInterpolation.setMaxSpeed(
				DEFAULT_FLYTO_ROT_MAXSPEED);
		
		archCamController = new ArchInterpolationController(cameraNode,
				archLocInterpolation, archRotInterpolation,
				worldUp, strategicHandler.getScrollingPlane(),
				strategicHandler.getHeightMap());
		archCamController.setArchFactor(DEFAULT_FLYTO_ARCH_FACTOR);
		archCamController.setHeightOffset(
				strategicHandler.getCamHeightOffset());
		cameraNode.addController(archCamController);
		
		/*
		 * these point directly to the handler's variables
		 * and should not be modified
		*/
		camPosition = strategicHandler.getCameraLocation();
		camLookAt = strategicHandler.getWorldTranslation();
	}
	
	public StrategicHandler getStrategicHandler() {
		return strategicHandler;
	}
	
	/**
	 * @see org.lex.input.CameraEffects#setCamera(com.jme.renderer.Camera)
	 */
	public void setCamera(Camera camera) {
		cameraNode.setCamera(camera);
	}
	
	/**
	 * @see org.lex.input.CameraEffects#getCamera()
	 */
	public Camera getCamera() {
		return cameraNode.getCamera();
	}
	
	/**
	 * @see org.lex.input.CameraEffects#update(float)
	 */
	public void update(float time) {
		if (archCamController.isActive()) {
			archCamController.setDesiredPosition(camPosition);
			archCamController.setDesiredLookAt(camLookAt);
		} else if (camController.isActive()) {
			camController.setDesiredPosition(camPosition);
			camController.setDesiredLookAt(camLookAt);
		} else {
			cameraNode.getLocalTranslation().set(camPosition);
			// this is a hack to make lookAt behave as expected
			cameraNode.getWorldTranslation().set(camPosition);
			cameraNode.lookAt(camLookAt, worldUp);
		}
		
		cameraNode.updateWorldData(time);
	}
	
	/**
	 * Call this to smoothly sync the camera position and orientation with
	 * strategicHandler when switching from another InputHandler. 
	 */
	public void takeOverCamera() {
		Camera cam = cameraNode.getCamera();

		// init handler's coordinates
		if (strategicHandler.getFollowObject() == null) {
			sphere.getHorizontal().setCurrent(
					sphere.computeHorizontalAngle(cam.getDirection()));
			Vector3f onSphere = sphere.findOnSphereLocation();
			Vector3f newCenter = cam.getLocation().subtract(onSphere, temp);
			plane.setLocationFromWolrdTranslation(newCenter);
			strategicHandler.updateCameraLocation();
		}
		
		// init camera node
		cameraNode.setLocalTranslation(cam.getLocation());
		cameraNode.getLocalRotation().lookAt(
				cam.getDirection(), worldUp);
		
		// init controller
		camController.setDesiredPosition(camPosition);
		camController.setDesiredLookAt(camLookAt);
		camController.startInterpolation();
		
		archCamController.setActive(false);
	}
	
	public void flyToLocation(Vector3f point) {
		strategicHandler.getScrollingPlane().projectOntoPlane(point, location);
		flyToLocation();
	}
	
	public void flyToLocation(Vector2f point) {
		location.set(point);
		flyToLocation();
	}
	
	private void flyToLocation() {
		if (strategicHandler.getFollowObject() != null) {
			archCamController.setActive(false);
			camController.setActive(false);
			return;
		}
		
		Camera cam = cameraNode.getCamera();
		ScrollingPlane plane = strategicHandler.getScrollingPlane();
		RotationSphere rotS = strategicHandler.getRotationSphere();
		
		plane.setLocation(location);
		
		Vector3f flyTo = plane.getWorldTranslation();
		Vector3f direction = flyTo.subtract(cam.getLocation(), temp);
		rotS.getHorizontal().setCurrent(
				rotS.computeHorizontalAngle(direction));
		
		strategicHandler.updateCameraLocation();
		
		// init camera node
		//cameraNode.setLocalTranslation(cam.getLocation());
		cameraNode.getLocalTranslation().set(cam.getLocation());
		cameraNode.getLocalRotation().lookAt(
				cam.getDirection(), worldUp);
		
		// init controller
		archCamController.setDesiredPosition(camPosition);
		archCamController.setDesiredLookAt(camLookAt);
		archCamController.startInterpolation();
		
		camController.setActive(false);
	}
	

	// *** Config **************************************************************
	
	@SuppressWarnings("unchecked")
	public void updateProperties(HashMap props) {
		locInterpolation.setSpringConstant(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_LOC_SPRING,
				locInterpolation.getSpringConstant()));
		locInterpolation.setDamping(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_LOC_DAMPING,
				locInterpolation.getDamping()));
		locInterpolation.setAccelerationFactor(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_LOC_ACCELFACTOR,
				locInterpolation.getAccelerationFactor()));
		locInterpolation.setMaxSpeed(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_LOC_MAXSPEED,
				locInterpolation.getMaxSpeed()));
		
		rotInterpolation.setSpringConstant(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_ROT_SPRING,
				rotInterpolation.getSpringConstant()));
		rotInterpolation.setDamping(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_ROT_DAMPING,
				rotInterpolation.getDamping()));
		rotInterpolation.setAccelerationFactor(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_ROT_ACCELFACTOR,
				rotInterpolation.getAccelerationFactor()));
		rotInterpolation.setMaxSpeed(InputHandler.getFloatProp(
				props, PROP_TAKEOVER_ROT_MAXSPEED,
				rotInterpolation.getMaxSpeed()));
		
		archLocInterpolation.setSpringConstant(InputHandler.getFloatProp(
				props, PROP_FLYTO_LOC_SPRING,
				archLocInterpolation.getSpringConstant()));
		archLocInterpolation.setDamping(InputHandler.getFloatProp(
				props, PROP_FLYTO_LOC_DAMPING,
				archLocInterpolation.getDamping()));
		archLocInterpolation.setAccelerationFactor(InputHandler.getFloatProp(
				props, PROP_FLYTO_LOC_ACCELFACTOR,
				archLocInterpolation.getAccelerationFactor()));
		archLocInterpolation.setMaxSpeed(InputHandler.getFloatProp(
				props, PROP_FLYTO_LOC_MAXSPEED,
				archLocInterpolation.getMaxSpeed()));
		
		archRotInterpolation.setSpringConstant(InputHandler.getFloatProp(
				props, PROP_FLYTO_ROT_SPRING,
				archRotInterpolation.getSpringConstant()));
		archRotInterpolation.setDamping(InputHandler.getFloatProp(
				props, PROP_FLYTO_ROT_DAMPING,
				archRotInterpolation.getDamping()));
		archRotInterpolation.setAccelerationFactor(InputHandler.getFloatProp(
				props, PROP_FLYTO_ROT_ACCELFACTOR,
				archRotInterpolation.getAccelerationFactor()));
		archRotInterpolation.setMaxSpeed(InputHandler.getFloatProp(
				props, PROP_FLYTO_ROT_MAXSPEED,
				archRotInterpolation.getMaxSpeed()));
		
		archCamController.setArchFactor(InputHandler.getFloatProp(
				props, PROP_FLYTO_ARCH_FACTOR,
				archCamController.getArchFactor()));
	}

	
	// *** Property keys *******************************************************
	
	public static final String PROP_TAKEOVER_LOC_SPRING = "takeLocSpring";
	public static final String PROP_TAKEOVER_LOC_DAMPING = "takeLocDamping";
	public static final String PROP_TAKEOVER_LOC_ACCELFACTOR = "takeLocAccel";
	public static final String PROP_TAKEOVER_LOC_MAXSPEED = "takeLocMaxSpeed";
	public static final String PROP_TAKEOVER_ROT_SPRING = "takeRotSpring";
	public static final String PROP_TAKEOVER_ROT_DAMPING = "takeRotDamping";
	public static final String PROP_TAKEOVER_ROT_ACCELFACTOR = "takeRotAccel";
	public static final String PROP_TAKEOVER_ROT_MAXSPEED = "takeRotMaxSpeed";
	
	public static final String PROP_FLYTO_LOC_SPRING = "flyLocSpring";
	public static final String PROP_FLYTO_LOC_DAMPING = "flyLocDamping";
	public static final String PROP_FLYTO_LOC_ACCELFACTOR = "flyLocAccel";
	public static final String PROP_FLYTO_LOC_MAXSPEED = "flyLocMaxSpeed";
	public static final String PROP_FLYTO_ROT_SPRING = "flyRotSpring";
	public static final String PROP_FLYTO_ROT_DAMPING = "flyRotDamping";
	public static final String PROP_FLYTO_ROT_ACCELFACTOR = "flyRotAccel";
	public static final String PROP_FLYTO_ROT_MAXSPEED = "flyRotMaxSpeed";
	public static final String PROP_FLYTO_ARCH_FACTOR = "flyArchFactor";
}
